/** @file         chrdev_driver.c
 *  @brief        简要说明
 *  @details      详细说明
 *  @author       lzm
 *  @date         2021-02-26 10:17:03
 *  @version      v1.0
 *  @copyright    Copyright By lizhuming, All Rights Reserved
 *
 **********************************************************
 *  @LOG 修改日志:
 **********************************************************
*/

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>


/* 常量 */
#define DEV_NAME "charDev" // 设备名称
#define DEV_CNT  (3)       // 设备数量
#define DEV_INODE_NAME "chrdev" // 设备节点名称前缀
#define DEV_CLASS_NAME "charDev_class" // 设备类名称

#define BUFF_SIZE 128      // 缓冲区大小

/* chrdev id.设备数组下标，也是次设备号。 */
typedef enum
{
    eChrDev_1 = 0,
    eChrDev_2,
}eChrDev_ID;

/* 变量 */
static dev_t devNumStar; // 起始设备号
struct class *chrdev_class; // 设备类（用于创建各个设备节点）

/* 创建设备管理结构体 */
struct chr_dev
{
    // 设备代码 ID 。在这里也可以用作次设备号。用于定位设备数组下标。
    unsigned char ID;
    
    // 设备参数
    dev_t devNum; // 设备号
    struct cdev dev; // 字符设备结构体

    // 设备驱动数据
    char cBuf[BUFF_SIZE]; // 缓冲区
    unsigned int rIndex;   // 该文件缓冲区
    unsigned int wIndex;   // 该文件缓冲区

    // 设备驱动函数等等
    
};
static struct chr_dev CHR_DEV[DEV_CNT]; // 创建 DEV_CNT 个字符设备。


/* 1. 1.2 实现驱动程序 */

/** @brief  chrdev_open
  * @param 
  * @retval 
  * @author lzm
  */
static int chrdev_open(struct inode *inode, struct file *filp)
{
    printk("chrdev_open!\n");
    printk("device num is [%d]\n", inode->i_rdev); // 写其它驱动时可以根据不同的设备号作不同初始化
    
    return 0;
}

/** @brief  chrdev_release
  * @param 
  * @retval 
  * @author lzm
  */
static int chrdev_release(struct inode *inode, struct file *filp)
{
    printk("chrdev_release!");
    printk("device num is [%d]\n", inode->i_rdev); // 写其它驱动时可以根据不同的设备号作不同出口处理
    return 0;
}

/** @brief  chrdev_write
  * @param 
  * @retval 
  * @author lzm
  */
static ssize_t chrdev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos)
{
    int ret;
    int tmp = count ;
    unsigned char curMinDevNum = MINOR(filp->f_inode->i_rdev);

    if(curMinDevNum >= DEV_CNT)
       return 0;

    printk("write min device num is [%d]!\n", curMinDevNum);

    CHR_DEV[curMinDevNum].wIndex = 0; // 每次都从0开始写

    if (tmp > BUFF_SIZE - CHR_DEV[curMinDevNum].wIndex)
        tmp = BUFF_SIZE - CHR_DEV[curMinDevNum].wIndex; // 写入到满缓冲区即可

    ret = copy_from_user(CHR_DEV[curMinDevNum].cBuf, buf, tmp);
    CHR_DEV[curMinDevNum].wIndex += tmp;

    return count; // 返回本次写入了多少
}

/** @brief  chrdev_read
  * @param 
  * @retval 
  * @author lzm
  */
static ssize_t chrdev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos)
{
    int ret;
    //unsigned long p = *ppos; // 内部保存，与write中的*ppos是相互独立的
    int tmp = count;
    unsigned char curMinDevNum = MINOR(filp->f_inode->i_rdev);

    printk("read min device num is [%d]!\n", curMinDevNum);
    printk("read wIndex is [%d]", CHR_DEV[curMinDevNum].wIndex);

    CHR_DEV[curMinDevNum].rIndex = 0; // 每次都从0开始读

    // 只读已写的数据
    if(tmp > CHR_DEV[curMinDevNum].wIndex)
        tmp = CHR_DEV[curMinDevNum].wIndex;

    ret = copy_to_user(buf, CHR_DEV[curMinDevNum].cBuf, tmp);
    CHR_DEV[curMinDevNum].rIndex += tmp;

    return tmp;
}


/* 1. 1.1 填充好 file_operation */
static struct file_operations chr_dev_fops = 
{
    .owner   = THIS_MODULE,
    .open    = chrdev_open,
    .release = chrdev_release,
    .write   = chrdev_write,
    .read    = chrdev_read,
};



/* NO.1:Module Init */

/**
 * @brief  char device init
 * @param 
 * @retval 
 * @author lzm
 */
static int __init chrdev_init(void)
{
    int ret = 0;
    unsigned char i;

    printk("chrdev_init\n");

    /* 1. get device num */
    ret = alloc_chrdev_region(&devNumStar, 0, DEV_CNT, DEV_NAME);
    if(ret < 0)
    {
        printk("chrdev_init error for not get devNum!\n");
        return -1;
    }
    else
    {
        printk("get devNumStar[%d]\n",devNumStar);
    }

    /* 2. creat device class */
    chrdev_class = class_create(THIS_MODULE, DEV_CLASS_NAME);

    for(i=0; i<DEV_CNT; i++)
    {
        /* get ID and devNum */
        CHR_DEV[i].ID = i;
        CHR_DEV[i].devNum = MKDEV(MAJOR(devNumStar), i);
        CHR_DEV[i].rIndex = 0;
        CHR_DEV[i].wIndex = 0;

        /* 3. init cdev and bind file_operation */
        cdev_init(&CHR_DEV[i].dev, &chr_dev_fops);

        /* 4. register cdev for kernel */
        ret = cdev_add(&CHR_DEV[i].dev, CHR_DEV[i].devNum, 1);
        if(ret < 0)
        {
            printk("[%d] cdev_add error!\n", i);
            unregister_chrdev_region(CHR_DEV[i].devNum, 1); // 添加进kernel失败时，需要归还设备号
            return -1;
        }
        else
        {
            printk("[%d] cdev_add successful!\n", i);
        }

        /* 5. creat device inode */
        device_create(chrdev_class, NULL, CHR_DEV[i].devNum, NULL, DEV_INODE_NAME "%d", i); // /dev/chrdev(i)
    }

    return 1;
}
module_init(chrdev_init);

/* NO.2 Module exit! */

/** @brief  chrdev exit!
  * @param 
  * @retval 
  * @author lzm
  */
static void __exit chrdev_exit(void)
{
    unsigned char i;

    printk("chrdev_exit!\n");
    
    for(i=0; i<DEV_CNT; i++)
    {
        device_destroy(chrdev_class, CHR_DEV[i].devNum); // 1. 删除设备节点
        cdev_del(&CHR_DEV[i].dev); // 2. 从kernel中删除字符设备结构体
    }
    unregister_chrdev_region(devNumStar, DEV_CNT); // 3. 归还设备号
    class_destroy(chrdev_class); // 4. 删除设备类
}
module_exit(chrdev_exit);


/* NO.3 LICENSE and info */
MODULE_AUTHOR("lizhuming");
MODULE_LICENSE("GPL");




